#include "stdafx.h"
#include "uozoTcp2Udp.h"
#include "SwitchCore.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <random>
#include "dlg.h"


SwitchCore::SwitchCore()
{
	std::random_device rd;
	std::mt19937 gen(rd());
	std::uniform_int_distribution<> distrib(20000, 40000);

	northBasePort = static_cast<unsigned short>(distrib(gen));
}

SwitchCore::~SwitchCore()
{
	stop();
}

void SwitchCore::start() {
	if (running.exchange(true)) 
		return;

	spdlog::info("start uozo tcp2udp services");
	threadPool_.enqueue([this]() {
		WorkerThread();
	});
}

void SwitchCore::stop() {
	if (!running.exchange(false))
		return;

	//if (m_workThread.joinable()) {
	//	m_workThread.join();
	//}

	south.stop();
	north.clearClients();
	m_map.clear();
}

void SwitchCore::WorkerThread() {
	CDlg* pMainDlg = afxDlg;

	try {
		std::ostringstream oss;
		oss << "set north udp svrport: " << pMainDlg->northPort << ", local base port: " << northBasePort;
		spdlog::info(oss.str());
		north.config(pMainDlg->northHost, pMainDlg->northPort, northBasePort);
		north.setCallback([this](IOCP::ClientID cid, const std::vector<char>& data) {
			this->OnNorthClientData(cid, data);
		});

		std::string southSid = "southTCP";
		south.setServerID(southSid);
		oss.str("");
		oss.clear();
		oss << "south tcp server sid: " << southSid;
		spdlog::info(oss.str());

		south.setConnectCallback([this](IOCP::ServerID sid, IOCP::ClientID cid, sockaddr_in addr) {
			this->OnSouthClientConnect(sid, cid, addr);
		});
		south.setDataCallback([this](IOCP::ServerID sid, IOCP::ClientID cid, sockaddr_in addr, const std::vector<char>& data) {
			this->OnSouthClientData(sid, cid, addr, data);
		});
		south.setDisconnectCallback([this](IOCP::ServerID sid, IOCP::ClientID cid, sockaddr_in addr) {
			this->OnSouthClientDisconnect(sid, cid, addr);
		});

		oss.str("");
		oss.clear();
		if (south.start(pMainDlg->southPort)) {
			oss << "start south tcp server on port " << pMainDlg->southPort << " ok";
			spdlog::info(oss.str());
		} 
		else {
			oss << "start south tcp server on port " << pMainDlg->southPort << " fail";
			spdlog::error(oss.str());
			return;
		}
	} catch (const std::exception& ex) {
		std::ostringstream oss;
		oss << "Exception in WorkerThread: " << ex.what();
		spdlog::error(oss.str());
	}
}

void SwitchCore::OnNorthClientData(IOCP::ClientID cid, const std::vector<char>& data) {
	if (!m_map.containsB(cid)) {
		std::ostringstream oss;
		oss << "route peer with north cid " << cid << " not founded";
		spdlog::error(oss.str());
		return;
	}

	IOCP::ClientID southCid = m_map.getAFromB(cid);
	south.sendToClient(southCid, data);
	
	bool sendShowEnable = false;
	bool recvShowEnable = false;	
	{
		std::lock_guard<std::mutex> lock(mutex);
		if (hotCid == southCid && showSendSouthEnable)
			sendShowEnable = true;
		if (hotCid == southCid && !showRcvSouthEnable)
			recvShowEnable = true;
	}
	if (sendShowEnable) {
		threadPool_.enqueue([this, data] {
			std::lock_guard<std::mutex> lock(mutex);
			sendQueue.push(std::move(data));
		});
	}
	if (recvShowEnable) {
		threadPool_.enqueue([this, data] {
			std::lock_guard<std::mutex> lock(mutex);
			recvQueue.push(std::move(data));
		});
	}
}

void SwitchCore::EnableRecvSouthShow(bool enable) {
	std::lock_guard<std::mutex> lock(mutex);

	showRcvSouthEnable = enable;

	std::queue<std::vector<char>> tempQueue;
	std::swap(tempQueue, recvQueue);
}

void SwitchCore::EnableSendSouthShow(bool enable) {
	std::lock_guard<std::mutex> lock(mutex);

	showSendSouthEnable = enable;

	std::queue<std::vector<char>> tempQueue;
	std::swap(tempQueue, sendQueue);
}

void SwitchCore::OnSouthClientConnect(IOCP::ServerID sid, IOCP::ClientID cid, sockaddr_in addr) {
	std::ostringstream oss;

	if (m_map.containsA(cid)) {
		oss << "south tcp error: dup-cid " << cid << "online";
		spdlog::error(oss.str());
	} 
	else {
		char ipBuf[INET_ADDRSTRLEN] = { 0 };
		inet_ntop(AF_INET, &addr.sin_addr, ipBuf, sizeof(ipBuf));
		unsigned int port = ntohs(addr.sin_port);
		oss << "south new cid: " << cid << " online at " << ipBuf << " : " << port;
		spdlog::info(oss.str());

		IOCP::ClientID northCid = north.createClient();
		m_map.insert(cid, northCid);
		oss.str("");
		oss.clear();
		oss << "set new route : south cid " << cid << " to north cid " << northCid;
		spdlog::info(oss.str());

		threadPool_.enqueue([this, cid, addr] {
			ListCtrlUpdate event;
			event.type = ListCtrlUpdate::INSERT;
			event.cid = cid;
			event.addr = addr;

			std::lock_guard<std::mutex> lock(mutex);
			eventQueue.push(std::move(event));
		});
	}
}

void SwitchCore::OnSouthClientData(IOCP::ServerID sid, IOCP::ClientID cid, sockaddr_in addr, const std::vector<char>& data) {
	if (!m_map.containsA(cid)) {
		std::ostringstream oss;
		oss << "south cid " << cid << " route not founded";
		spdlog::error(oss.str());
		return;
	}

	IOCP::ClientID northCid = m_map.getBFromA(cid);
	north.sendData(northCid, data);

	bool sendShowEnable = false;
	bool recvShowEnable = false;
	{
		std::lock_guard<std::mutex> lock(mutex);
		if (hotCid == cid && !showSendSouthEnable)
			sendShowEnable = true;
		if (hotCid == cid && showRcvSouthEnable)
			recvShowEnable = true;
	}
	if (sendShowEnable) {
		threadPool_.enqueue([this, data] {
			std::lock_guard<std::mutex> lock(mutex);
			sendQueue.push(std::move(data));
		});
	}
	if (recvShowEnable) {
		threadPool_.enqueue([this, data] {
			std::lock_guard<std::mutex> lock(mutex);
			recvQueue.push(std::move(data));
		});
	}
}

void SwitchCore::OnSouthClientDisconnect(IOCP::ServerID sid, IOCP::ClientID cid, sockaddr_in addr) {
	std::ostringstream oss;

	if (!m_map.containsA(cid)) {
		oss << "in disconnect, south cid " << cid << " route not founded";
		spdlog::error(oss.str());
		return;
	}

	IOCP::ClientID northCid = m_map.getBFromA(cid);
	north.closeClient(northCid);
	m_map.removeByA(cid);
	oss << "remove route from south cid " << cid << " to north cid " << northCid << " ok" << ", then route count " << m_map.size();
	spdlog::info(oss.str());

	threadPool_.enqueue([this, cid] {
		ListCtrlUpdate event;
		event.type = ListCtrlUpdate::REMOVE;
		event.cid = cid;

		std::lock_guard<std::mutex> lock(mutex);
		eventQueue.push(std::move(event));
	});
}
